/*
 * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform".
 *
 * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved.
 *
 * Please visit http://javatelnet.org/ for updates and contact.
 *
 * --LICENSE NOTICE--
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * --LICENSE NOTICE--
 *
 */

package de.mud.jta.plugin;

import de.mud.jta.Plugin;
import de.mud.jta.FilterPlugin;
import de.mud.jta.PluginBus;
import de.mud.jta.PluginConfig;
import de.mud.jta.event.ConfigurationListener;
import de.mud.jta.event.OnlineStatusListener;

import java.io.IOException;

import java.util.Vector;

/**
 * The script plugin takes a series of match and answer pairs to compare the
 * incoming data with the matches and if it succeeds writes the answers back. It
 * then moves on to the next match until all script elements have matched.
 * <P>
 * The script property Script.script should contain <B>|</B> separated strings
 * where each two represent a match and answer pair. A newline will be appended
 * to each answer!
 * <P>
 * If the first matching string is empty, the answer string will be sent upon
 * connect. The script is very basic but is a very good example how to write a
 * plugin for <B>JTA - Telnet/SSH for the JAVA(tm) platform</B>.
 * <P>
 * <B>Maintainer:</B> Matthias L. Jugel
 * 
 * @version $Id: Script.java 499 2005-09-29 08:24:54Z leo $
 * @author Matthias L. Jugel, Marcus Mei�ner
 */
public class Script extends Plugin implements FilterPlugin
{

    /** debugging level */
    private final static int debug = 0;

    /** the script after parsing, saved for reinitialization */
    private Vector savedScript;

    /**
     * Create a new scripting plugin.
     */
    public Script(PluginBus bus, final String id)
    {
	super(bus, id);

	bus.registerPluginListener(new OnlineStatusListener()
	{
	    public void online()
	    {
		setup(savedScript);
	    }

	    public void offline()
	    {
		// ignore disconnection
	    }
	});

	bus.registerPluginListener(new ConfigurationListener()
	{
	    public void setConfiguration(PluginConfig config)
	    {
		savedScript = new Vector();
		String s = config.getProperty("Script", id, "script");
		if (s != null)
		{
		    // check if the script is stored in a file
		    if (s.charAt(0) == '@')
		    {
			Script.this.error("@file not implemented yet");
		    }
		    // parse the script and set up
		    if (debug > 0)
			Script.this.error(s);
		    String pair[] = null;
		    int old = -1, idx = s.indexOf('|');
		    while (idx >= 0)
		    {
			if (pair == null)
			{
			    pair = new String[2];
			    pair[0] = s.substring(old + 1, idx);
			    if (debug > 0)
				System.out.print("match(" + pair[0] + ") -> ");
			}
			else
			{
			    pair[1] = s.substring(old + 1, idx) + "\n";
			    if (debug > 0)
				System.out.print(pair[1]);
			    savedScript.addElement(pair);
			    pair = null;
			}
			old = idx;
			idx = s.indexOf('|', old + 1);
		    }
		    if (pair != null)
		    {
			pair[1] = s.substring(old + 1) + "\n";
			savedScript.addElement(pair);
			if (debug > 0)
			    System.out.print(pair[1]);
		    }
		    else
			Script.this.error("unmatched pairs of script elements");
		    // set up the script
		    // setup(savedScript);
		}
	    }
	});
    }

    /** holds the data source for input and output */
    protected FilterPlugin source;

    /**
     * Set the filter source where we can read data from and where to write the
     * script answer to.
     * 
     * @param plugin
     *            the filter plugin we use as source
     */
    public void setFilterSource(FilterPlugin plugin)
    {
	source = plugin;
    }

    public FilterPlugin getFilterSource()
    {
	return source;
    }

    /**
     * Read an array of bytes from the back end and put it through the script
     * parser to see if it matches. It will send the answer immediately to the
     * filter source if a match occurs.
     * 
     * @param b
     *            the array where to read the bytes in
     * @return the amount of bytes actually read
     */
    public int read(byte[] b) throws IOException
    {
	int n = source.read(b);
	if (n > 0)
	    match(b, n);
	return n;
    }

    public void write(byte[] b) throws IOException
    {
	source.write(b);
    }

    // =================================================================
    // the actual scripting code follows:
    // =================================================================

    private int matchPos; // current position in the match
    private Vector script; // the actual script
    private byte[] match; // the current bytes to look for
    private boolean done = true; // no script!

    /**
     * Setup the parser using the passed script. The script contains for every
     * element a two-element array of String where element zero contains the
     * match and element one the answer.
     * 
     * @param script
     *            the script
     */
    private void setup(Vector script)
    {
	// clone script to make sure we do not change the original
	this.script = (Vector) script.clone();
	if (debug > 0)
	    System.err.println("Script: script contains " + script.size()
		    + " elements");

	// If the first element is empty, just send the value string.
	match = ((String[]) this.script.firstElement())[0].getBytes();
	if (match.length == 0)
	{
	    try
	    {
		write(found());
	    }
	    catch (Exception e)
	    {
		// Ignore any errors here
	    }
	    ;
	}

	reset();
	done = false;
    }

    /**
     * Try to match the byte array s against the most current script match. It
     * will write the answer immediatly if the script matches and will return
     * instantly when all the script work is done.
     * 
     * @param s
     *            the array of bytes to match against
     * @param length
     *            the amount of bytes in the array
     */
    private void match(byte[] s, int length) throws IOException
    {
	for (int i = 0; !done && i < length; i++)
	{
	    if (s[i] == match[matchPos])
	    {
		// the whole thing matched so, return the match answer
		// and reset to use the next match
		if (++matchPos >= match.length)
		    write(found());
	    }
	    else
		// if the current character did not match reset
		reset();
	}
    }

    /**
     * This method is called when a script match was found and will setup the
     * next match to be used and return the answer for the just found one.
     * 
     * @return the answer to the found match
     */
    private byte[] found()
    {
	if (debug > 0)
	    System.err.println("Script: found '" + new String(match) + "'");
	// we have matched the string, so remember the answer ...
	byte[] answer = ((String[]) script.firstElement())[1].getBytes();
	// remove the matched element
	script.removeElementAt(0);
	// set the next match if applicable
	if (!script.isEmpty())
	{
	    match = ((String[]) script.firstElement())[0].getBytes();
	    reset();
	}
	else
	    done = true;
	return answer;
    }

    /**
     * Reset the match position counter.
     */
    private void reset()
    {
	matchPos = 0;
    }
}
